(ns lensfield.file-utils
   (:import (java.io File)
            (java.io File FileOutputStream)
            (org.apache.commons.io FileUtils FilenameUtils IOUtils)))

(defmulti as-file class)
(defmethod as-file String [#^String f] (new File f))
(defmethod as-file File [#^File f] f)

(defmulti as-path class)
(defmethod as-path String [#^String p] (as-path (as-file p)))
(defmethod as-path File [#^File p] (FilenameUtils/separatorsToUnix (. p getCanonicalPath)))

(defn unix-filepath
  "Gets the Unix-like filepath of the provided File/String."
  [fl]
  (FilenameUtils/separatorsToUnix (as-path fl)))
  
(defn mkdir
   "Creates the directory named by this abstract pathname."
   [dir]
   (. (as-file dir) mkdir))
   
(defn mkdirs
   "Creates the directory named by this abstract pathname, 
   including any necessary but nonexistent parent directories."
   [dir]
   (. (as-file dir) mkdirs))
  
(defn create-file
   "Atomically creates a new, empty file named by this abstract 
   pathname if and only if a file with this name does not yet exist."
   [fl]
   (. (as-file fl) createNewFile))
   
(defn delete-file
   "Deletes a file, never throwing an exception."
   [fl]
   (FileUtils/deleteQuietly (as-file fl)))
   
(defn delete-dir
   "Deletes a directory recursively."
   [dir]
   (FileUtils/deleteDirectory (as-file dir)))
  
(defn file-exists
   "Returns boolean stating whether file exists or not."
   [fl]
   (. (as-file fl) exists))
  
(defn copy-file
   "Copies a file to a new location."
   [from to]
   (FileUtils/copyFile (as-file from) (as-file to)))    
  
(defn copy-file-to-dir
   "Copies a file to a directory preserving the file date."
   [fl dir]
   (FileUtils/copyFileToDirectory (as-file fl) (as-file dir)))

(defn copy-dir
   "Copies a whole directory to a new location."
   [from to]
   (FileUtils/copyDirectory (as-file from) (as-file to)))
  
(defn copy-dir-to-dir
   "Copies a directory to within another directory preserving the file dates."
   [from to]
   (FileUtils/copyDirectoryToDirectory (as-file from) (as-file to)))
   
(defn move-file
   "Moves a file."
   [from to]
   (FileUtils/moveFile (as-file from) (as-file to)))

(defmacro with-out-file
   "Evaluates exprs in a context in which *out* is bound to a fresh
FileWriter for file.  Returns the string created by any nested printing
calls."
   [fl & body]
   `(let [f# (java.io.FileWriter. (as-file ~fl))]
      (binding [*out* f#]
         (try
            ~@body
            (finally
               (org.apache.commons.io.IOUtils/closeQuietly f#))))))

(defmacro write-forms
   "Writes out the data structures passed as forms to the provided file."
   [fl & forms]
   `(with-out-file ~fl (pr ~@forms)))

(defn get-filename
   "Gets the filename from a file path, e.g. c:/dir/example.txt gives
example.txt"
   [fl]
   (FilenameUtils/getName (as-path fl)))

(defn get-basename
   "Gets file basename from a filepath, e.g. c:/dir/example.txt gives
example."
   [fl]
   (let [n (FilenameUtils/getName (as-path fl))]
      (.substring n 0 (.indexOf n "."))))
      
(defn move-file-to-dir
   "Moves a file to a directory, optionally creating the dir."
   [fl dir create-dir]
   (FileUtils/moveFileToDirectory (as-file fl) (as-file dir) create-dir)) 
   
(defn move-file-to-own-dir
   "Creates a dir within the files parent dir with the same name as the file
   and moves the file into it."
   [fl]
   (let [fl (as-file fl)
         basename (get-basename fl)
         parent-filepath (as-path (.getParentFile fl))
         new-parent (str parent-filepath "/" basename)]
      (move-file-to-dir fl new-parent true)))

(defn get-filepath-minus-extension
   "Gets the full path of the provided file, minus the file extension, e.g.
c:/dir/file.txt would give c:/dir/file"
   [fl]
   (let [p (as-path fl)]
      (str (.substring p 0 (+ 1 (.lastIndexOf p "/"))) (get-basename p))))

(defn get-extension
   "Gets the extension from a filepath, e.g. c:/dir/test.xml gives xml.
Note that for multiple extensions, e.g. test.cml.xml, only the last
will be returned (in this case 'xml')."
   [fl]
   (let [n (get-filename (as-path fl))]
      (.substring n (+ 1 (.lastIndexOf n ".")))))

(defn is-file?
   "Predicate that returns true if the file argument is a file rather than a directory"
   [#^File f]
   (.isFile f))

(defn is-dir?
   "Predicate that returns true if the file argument is a directory."
   [#^File f]
   (.isDirectory f))

(defn append-to-file
   "Appends a string (provided as content) to the provided file."
   [file content]
   (with-open [fos (FileOutputStream. (as-file file) true)]
      (.write fos (.getBytes content))))   